package com.ccteam.fluidmusic.fluidmusic.common.utils

import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.util.Size
import androidx.annotation.DrawableRes
import androidx.collection.LruCache
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.request.RequestOptions
import com.ccteam.fluidmusic.fluidmusic.R
import com.orhanobut.logger.Logger
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.Exception
import kotlin.math.min

/**
 *
 * @author Xiaoc
 * @since 2021/1/5
 *
 * 存储封面Bitmap的单例
 * 将Bitmap存放在LruCache中，当超出容量后，自动释放最少用的Bitmap
 *
 * 该内容为单例，归由Hilt管理
 */
@Singleton
class AlbumArtCache @Inject constructor(
    @ApplicationContext private val context: Context
) {

    companion object{
        private const val ART_BITMAP_SIZE = 650

        private const val ICON_BITMAP_SIZE = 250

        /**
         * 24MB的容量
         */
        private const val MAX_ALBUM_ART_CACHE_SIZE = 24 * 1024 * 1024

        /**
         * 专辑封面存储的Bitmap高清图
         */
        private const val ART_BITMAP_INDEX = 0

        /**
         * 专辑封面存储的Bitmap小图
         */
        private const val ICON_BITMAP_INDEX = 1
    }

    private val glideOptions = RequestOptions()
        .diskCacheStrategy(DiskCacheStrategy.DATA)

    /**
     * LruCache最大容量，需要与内存所需进行比较，如果当前内存的 四分之一 高于 18M，则使用标定的18M容量
     */
    private val maxSize = min(
        MAX_ALBUM_ART_CACHE_SIZE,
        min(Int.MAX_VALUE.toLong(), Runtime.getRuntime().maxMemory() / 4).toInt()
    )

    private val bitmapCache = object : LruCache<String, Array<Bitmap>>(maxSize) {
        override fun sizeOf(key: String, value: Array<Bitmap>): Int =
            value[ART_BITMAP_INDEX].byteCount + value[ICON_BITMAP_INDEX].byteCount
    }

    /**
     * 解析Bitmap，通过专辑封面地址来解析
     * 需要传入额外参数
     * @param cacheKeyUri 存储的键的内容
     * @param realParseUri 真正解析图片的Uri
     * @param isFull 是否是全高清图片
     * @param disPrepared 暂时未准备好或准备失败的回调
     * @param prepared 已经准备好，回传Bitmap
     *
     * 该方法使用协程，更高效处理
     * 当LruCache中存在时则会直接返回对应Bitmap
     * 如果不存在则会开启协程解析Bitmap
     * 这里由于Android Q的API改动，分别提供存储的键和解析的键隔离开
     * 还做了离线和在线区分，离线是不会延迟的，所以可以直接返回对应专辑封面
     * 在线音乐需要先返回isLoading状态再进行对应内容返回
     * 方便对应出现预加载图
     */
    suspend fun resolveBitmapByUri(
        cacheKeyUri: Uri,
        realParseUri: Uri,
        isFull: Boolean,
        disPrepared: (isLoading: Boolean) -> Unit,
        prepared: (Bitmap) -> Unit
    ){
        getAlbumArt(cacheKeyUri,isFull)?.let {
            prepared(it)
            return
        }
        try {
            // 如果是本地的Uri资源，则直接解析
            if(realParseUri.isLocalMedia()){
                    withContext(Dispatchers.IO){
                        return@withContext resolveLocalBitmap(realParseUri,cacheKeyUri,isFull)
                    }?.let {
                        prepared(it)
                    }?: run {
                        disPrepared(false)
                    }
            } else {
                // 网络图片先返回正在加载的回调
                disPrepared(true)
                withContext(Dispatchers.IO){
                    return@withContext resolveOnlineBitmap(realParseUri,cacheKeyUri,isFull)
                }?.let {
                    prepared(it)
                }?: run {
                    disPrepared(false)
                }
            }
        } catch (e: Exception){
            Logger.e("获取专辑封面失败 ${e.message}")
            disPrepared(false)
        }

    }

    suspend fun resolveResourceToBitmap(@DrawableRes res: Int): Bitmap{
        return withContext(Dispatchers.IO){
            Glide.with(context)
                .asBitmap()
                .load(R.drawable.exo_ic_default_album_image)
                .submit(ICON_BITMAP_SIZE,ICON_BITMAP_SIZE)
                .get()
        }
    }

    /**
     * 通过封面uri获取封面Bitmap
     */
    private fun getAlbumArt(artUri: Uri, isFull: Boolean): Bitmap? {
        val index = if (isFull) ART_BITMAP_INDEX else ICON_BITMAP_INDEX
        return bitmapCache[artUri.toString()]?.get(index)
    }

    /**
     * 解析本地Uri的Bitmap
     */
    private fun resolveLocalBitmap(realParseUri: Uri, cacheKeyUri: Uri, isFull: Boolean = false): Bitmap? {
            // 如果是本地MediaStore的Uri且AndroidQ以上，使用自带API进行获取，否则全部Glide获取
            if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q){
                val largeBitmap = context.contentResolver.loadThumbnail(
                    realParseUri,
                    Size(ART_BITMAP_SIZE, ART_BITMAP_SIZE),
                    null
                )
                val iconBitmap = scaleBitmap(largeBitmap, ICON_BITMAP_SIZE)
                bitmapCache.put(cacheKeyUri.toString(), arrayOf(largeBitmap, iconBitmap))
            } else {
                val largeBitmap =
                    Glide.with(context).applyDefaultRequestOptions(glideOptions)
                        .asBitmap()
                        .load(realParseUri)
                        .submit(ART_BITMAP_SIZE, ART_BITMAP_SIZE)
                        .get()
                val iconBitmap = scaleBitmap(largeBitmap, ICON_BITMAP_SIZE)
                bitmapCache.put(cacheKeyUri.toString(), arrayOf(largeBitmap, iconBitmap))
            }
            return getAlbumArt(cacheKeyUri,isFull)
    }

    /**
     * 通过Uri加载在线内容解析成Bitmap
     */
    private fun resolveOnlineBitmap(realParseUri: Uri, cacheKeyUri: Uri, isFull: Boolean = false): Bitmap?  {
        val largeBitmap = Glide.with(context).applyDefaultRequestOptions(glideOptions)
            .asBitmap()
            .load(realParseUri)
            .submit(ART_BITMAP_SIZE, ART_BITMAP_SIZE)
            .get()
        val iconBitmap = scaleBitmap(largeBitmap, ICON_BITMAP_SIZE)
        bitmapCache.put(cacheKeyUri.toString(), arrayOf(largeBitmap, iconBitmap))
        return getAlbumArt(cacheKeyUri,isFull)
    }


    private fun scaleBitmap(bitmap: Bitmap, maxSize: Int): Bitmap {
        val scaleFactor = min(maxSize.toDouble() / bitmap.width, maxSize.toDouble() / bitmap.height)
        return Bitmap.createScaledBitmap(
            bitmap,
            (bitmap.width * scaleFactor).toInt(),
            (bitmap.height * scaleFactor).toInt(),
            false
        )
    }
}